﻿#include "asycipc/session_context.hpp"

#include <array>

namespace asycipc {
namespace {
bool IsAllowed(SessionState current, SessionState next) {
  switch (current) {
    case SessionState::kCreated:
      return next == SessionState::kNegotiating || next == SessionState::kError;
    case SessionState::kNegotiating:
      return next == SessionState::kActive || next == SessionState::kError;
    case SessionState::kActive:
      return next == SessionState::kDraining || next == SessionState::kError ||
             next == SessionState::kClosed;
    case SessionState::kDraining:
      return next == SessionState::kClosed || next == SessionState::kError;
    case SessionState::kClosed:
    case SessionState::kError:
      return false;
  }
  return false;
}
}  // namespace

SessionContext::SessionContext() { Touch(); }

void SessionContext::SetSessionId(std::string id) { session_id_ = std::move(id); }

void SessionContext::TransitionTo(SessionState next_state) {
  if (!IsAllowed(state_, next_state)) {
    throw SessionStateError("invalid session state transition");
  }
  state_ = next_state;
  Touch();
}

uint64_t SessionContext::NextSendSeq() {
  const uint64_t seq = next_send_seq_++;
  Touch();
  return seq;
}

void SessionContext::AckReceived(uint64_t seq) {
  if (seq < last_acked_seq_) {
    throw SessionStateError("ack rewind detected");
  }
  last_acked_seq_ = seq;
  Touch();
}

void SessionContext::RegisterInbound(uint64_t seq) {
  if (seq != expected_recv_seq_) {
    throw SessionStateError("unexpected inbound sequence");
  }
  ++expected_recv_seq_;
  Touch();
}

void SessionContext::Touch() {
  last_activity_ = std::chrono::steady_clock::now();
}

}  // namespace asycipc
